package xin.petstore.framework.json;

import com.fasterxml.jackson.core.io.NumberInput;

import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class CustomerDateFormat extends DateFormat {
    protected static final String DATE_FORMAT_STR_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    protected static final String DATE_FORMAT_STR_ISO8601_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    protected static final String DATE_FORMAT_STR_PLAIN = "yyyy-MM-dd";
    protected static final String DATE_FORMAT_STR_PLAIN_ZH = "yyyy-MM-dd HH:mm:ss";
    protected static final String DATE_FORMAT_STR_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
    protected static final String[] ALL_FORMATS = {
            "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
            "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
            "EEE, dd MMM yyyy HH:mm:ss zzz",
            "yyyy-MM-dd",
            "yyyy-MM-dd HH:mm:ss"};

    private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT");

    protected static final DateFormat DATE_FORMAT_RFC1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
    protected static final DateFormat DATE_FORMAT_ISO8601;
    protected static final DateFormat DATE_FORMAT_ISO8601_Z;
    protected static final DateFormat DATE_FORMAT_PLAIN;
    public static final CustomerDateFormat instance;
    protected transient TimeZone _timezone;
    protected transient DateFormat _formatRFC1123;
    protected transient DateFormat _formatISO8601;
    protected transient DateFormat _formatISO8601_z;
    protected transient DateFormat _formatPlain;

    static {
        DATE_FORMAT_RFC1123.setTimeZone(DEFAULT_TIMEZONE);
        DATE_FORMAT_ISO8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        DATE_FORMAT_ISO8601.setTimeZone(DEFAULT_TIMEZONE);
        DATE_FORMAT_ISO8601_Z = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        DATE_FORMAT_ISO8601_Z.setTimeZone(DEFAULT_TIMEZONE);
        DATE_FORMAT_PLAIN = new SimpleDateFormat("yyyy-MM-dd");
        DATE_FORMAT_PLAIN.setTimeZone(DEFAULT_TIMEZONE);

        instance = new CustomerDateFormat();
    }

    public CustomerDateFormat() {
    }

    public CustomerDateFormat(TimeZone tz) {
        this._timezone = tz;
    }

    public static TimeZone getDefaultTimeZone() {
        return DEFAULT_TIMEZONE;
    }

    public CustomerDateFormat withTimeZone(TimeZone tz) {
        if (tz == null) {
            tz = DEFAULT_TIMEZONE;
        }
        return new CustomerDateFormat(tz);
    }

    @Override
    public CustomerDateFormat clone() {
        return new CustomerDateFormat();
    }

    public static DateFormat getBlueprintISO8601Format() {
        return DATE_FORMAT_ISO8601;
    }

    public static DateFormat getISO8601Format(TimeZone tz) {
        return _cloneFormat(DATE_FORMAT_ISO8601, tz);
    }

    public static DateFormat getBlueprintRFC1123Format() {
        return DATE_FORMAT_RFC1123;
    }

    public static DateFormat getRFC1123Format(TimeZone tz) {
        return _cloneFormat(DATE_FORMAT_RFC1123, tz);
    }

    @Override
    public void setTimeZone(TimeZone tz) {
        if (tz != this._timezone) {
            this._formatRFC1123 = null;
            this._formatISO8601 = null;
            this._formatISO8601_z = null;
            this._formatPlain = null;
            this._timezone = tz;
        }
    }

    @Override
    public Date parse(String dateStr) throws ParseException {

        dateStr = dateStr.trim();
        ParsePosition pos = new ParsePosition(0);
        StringBuilder sb = new StringBuilder();
        Date result = parse(dateStr, pos);
        if (result != null) {
            return result;
        }
        for (String f : ALL_FORMATS) {
            if (sb.length() > 0) {
                sb.append("\", \"");
            } else {
                sb.append('"');
            }
            sb.append(f);
        }
        sb.append('"');
        throw new ParseException(
                String.format("Can not parse date \"%s\": not compatible with any of standard forms (%s)", new Object[]{
                        dateStr, sb.toString()}), pos.getErrorIndex());
    }

    @Override
    public Date parse(String dateStr, ParsePosition pos) {
        if (looksLikeISO8601(dateStr)) {
            return parseAsISO8601(dateStr, pos);
        }

        int i = dateStr.length();
        do {
            char ch = dateStr.charAt(i);
            if ((ch < '0') || (ch > '9')) {
                break;
            }
            i--;
        } while (i >= 0);

        if ((i < 0) &&
                (NumberInput.inLongRange(dateStr, false))) {
            return new Date(Long.parseLong(dateStr));
        }

        return parseAsRFC1123(dateStr, pos);
    }

    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
        if (this._formatISO8601 == null) {
            this._formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601);
        }
        return this._formatISO8601.format(date, toAppendTo, fieldPosition);
    }

    protected boolean looksLikeISO8601(String dateStr) {
        return (dateStr.length() >= 5) &&
                (Character.isDigit(dateStr.charAt(0))) &&
                (Character.isDigit(dateStr.charAt(3))) &&
                (dateStr.charAt(4) == '-');
    }

    protected Date parseAsISO8601(String dateStr, ParsePosition pos) {
        int len = dateStr.length();
        char c = dateStr.charAt(len - 1);
        DateFormat df;
        if ((len <= 10) && (Character.isDigit(c))) {
            df = this._formatPlain;
            if (df == null)
                df = this._formatPlain = _cloneFormat(DATE_FORMAT_PLAIN);
        } else if (c == 'Z') {
            df = this._formatISO8601_z;
            if (df == null) {
                df = this._formatISO8601_z = _cloneFormat(DATE_FORMAT_ISO8601_Z);
            }

            if (dateStr.charAt(len - 4) == ':') {
                StringBuilder sb = new StringBuilder(dateStr);
                sb.insert(len - 1, ".000");
                dateStr = sb.toString();
            }

        } else if (hasTimeZone(dateStr)) {
            c = dateStr.charAt(len - 3);
            if (c == ':') {
                StringBuilder sb = new StringBuilder(dateStr);
                sb.delete(len - 3, len - 2);
                dateStr = sb.toString();
            } else if ((c == '+') || (c == '-')) {
                dateStr = dateStr + "00";
            }

            len = dateStr.length();

            c = dateStr.charAt(len - 9);
            if (Character.isDigit(c)) {
                StringBuilder sb = new StringBuilder(dateStr);
                sb.insert(len - 5, ".000");
                dateStr = sb.toString();
            }

            df = this._formatISO8601;
            if (this._formatISO8601 == null) {
                df = this._formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601);
            }

        } else {
            StringBuilder sb = new StringBuilder(dateStr);

            int timeLen = len - dateStr.lastIndexOf('T') - 1;
            if (timeLen <= 8) {
                sb.append(".000");
            }
            sb.append('Z');
            dateStr = sb.toString();
            df = this._formatISO8601_z;
            if (df == null) {
                df = this._formatISO8601_z = _cloneFormat(DATE_FORMAT_ISO8601_Z);
            }
        }
        return df.parse(dateStr, pos);
    }

    protected Date parseAsRFC1123(String dateStr, ParsePosition pos) {
        if (this._formatRFC1123 == null) {
            this._formatRFC1123 = _cloneFormat(DATE_FORMAT_RFC1123);
        }
        return this._formatRFC1123.parse(dateStr, pos);
    }

    private static final boolean hasTimeZone(String str) {
        int len = str.length();
        if (len >= 6) {
            char c = str.charAt(len - 6);
            if ((c == '+') || (c == '-')) return true;
            c = str.charAt(len - 5);
            if ((c == '+') || (c == '-')) return true;
            c = str.charAt(len - 3);
            if ((c == '+') || (c == '-')) return true;
        }
        return false;
    }

    private final DateFormat _cloneFormat(DateFormat df) {
        return _cloneFormat(df, this._timezone);
    }

    private static final DateFormat _cloneFormat(DateFormat df, TimeZone tz) {
        df = (DateFormat) df.clone();
        if (tz != null) {
            df.setTimeZone(tz);
        }
        return df;
    }
}